using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace ConvertSourceToHtml.Conversion.HtmlCodeElements
{

    /// <summary>
    /// Represents an HTML file.
    /// </summary>
    public sealed class HtmlFile
    {
        #region Fields

        private List<HtmlLine> _lines;
        private HtmlLine _currentLine;
        private Stack<HtmlBlock> _blocks;
        private HtmlBlock _currentBlock;

        #endregion // Fields

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="HtmlFile"/> class.
        /// </summary>
        public HtmlFile()
        {
            _lines = new List<HtmlLine>();
            _currentLine = new HtmlLine();
            _lines.Add(_currentLine);
            _blocks = new Stack<HtmlBlock>();
            _currentBlock = new HtmlBlock();
            _currentLine.AddBlock(_currentBlock);
        }

        #endregion // Constructors

        #region Properties

        /// <summary>
        /// Gets the lines that make up the file.
        /// </summary>
        public ReadOnlyCollection<HtmlLine> Lines
        {
            get { return new ReadOnlyCollection<HtmlLine>(_lines); }
        }

		/// <summary>
		/// Gets the number of indentation spaces for the file.
		/// </summary>
		/// <value>The number of indentation spaces for the file.</value>
		public int IndentationSpaces
		{
			get
			{
				int indentationSpaces;

				indentationSpaces = Int32.MaxValue;
				foreach (HtmlLine line in _lines)
				{
					if (line.HasContent && (line.LeadingSpaces < indentationSpaces))
					{
						indentationSpaces = line.LeadingSpaces;
					}
				}
				if (indentationSpaces == Int32.MaxValue)
				{
					indentationSpaces = 0;
				}
				return indentationSpaces;
			}
		}

		#endregion // Properties

        #region Methods

        /// <summary>
        /// Starts a new line.
        /// </summary>
        public void StartNewLine()
        {
            _currentLine = new HtmlLine();
            _lines.Add(_currentLine);
            StartNewBlock(_currentBlock);
        }

        /// <summary>
        /// Appends a character to the file.
        /// </summary>
        /// <param name="value">The character.</param>
        /// <param name="tabSize">The tab size.</param>
        /// <remarks>
        /// <para>
        /// The <paramref name="tabSize"/> parameter is used to get the necessary whitespace when 
        /// appending a non-whitespace character after trailing spaces.
        /// </para>
        /// </remarks>
        public void Append(char value, int tabSize)
        {
            _currentBlock.Append(value, tabSize);
        }

        /// <summary>
        /// Pushes the current block onto the stack and starts a new block based on the current block.
        /// </summary>
        public void PushBlock()
        {
            _blocks.Push(_currentBlock);
            StartNewBlock(_currentBlock);
        }

        /// <summary>
        /// Pops an old block off of the stack and starts a new block based on the old block.
        /// </summary>
        /// <remarks>
        /// <para>
        /// The old block is not reused and does not become the current block.  Instead, a new 
        /// block is started based on the old block, and having the old block's settings.
        /// </para>
        /// </remarks>
        public void PopBlock()
        {
            StartNewBlock(_blocks.Pop());
        }

        /// <summary>
        /// Sets the foreground color.
        /// </summary>
        /// <param name="foregroundColor">The foreground color.</param>
        /// <remarks>
        /// <para>
        /// If current block's foreground color is different than <paramref name="foregroundColor"/>,
        /// and if the current block has content or leading spaces, its foreground color will not
        /// be set.  Instead, the current block will be committed, a new block will be created with
        /// the same settings, the new block's foreground color will be set, and the new block will
        /// become the current block.
        /// </para>
        /// </remarks>
        public void SetForegroundColor(int foregroundColor)
        {
            if (_currentBlock.ForegroundColor != foregroundColor)
            {
                CommitBlock();
                _currentBlock.ForegroundColor = foregroundColor;
            }
        }

        /// <summary>
        /// Sets the background color.
        /// </summary>
        /// <param name="backgroundColor">The background color.</param>
        /// <remarks>
        /// <para>
        /// If current block's background color is different than <paramref name="backgroundColor"/>,
        /// and if the current block has content or leading spaces, its background color will not
        /// be set.  Instead, the current block will be committed, a new block will be created with
        /// the same settings, the new block's background color will be set, and the new block will
        /// become the current block.
        /// </para>
        /// </remarks>
        public void SetBackgroundColor(int backgroundColor)
        {
            if (_currentBlock.BackgroundColor != backgroundColor)
            {
                CommitBlock();
                _currentBlock.BackgroundColor = backgroundColor;
            }
        }

        /// <summary>
        /// Sets the bold state.
        /// </summary>
        /// <param name="bold">The bold state.</param>
        /// <remarks>
        /// <para>
        /// If current block's bold state is different than <paramref name="bold"/>, and if the 
        /// current block has content or leading spaces, its bold state will not be set.  Instead, 
        /// the current block will be committed, a new block will be created with the same 
        /// settings, the new block's bold state will be set, and the new block will become the 
        /// current block.
        /// </para>
        /// </remarks>
        public void SetBold(bool bold)
        {
            if (_currentBlock.Bold != bold)
            {
                CommitBlock();
                _currentBlock.Bold = bold;
            }
        }

        #endregion // Methods

        #region Implementation

        /// <summary>
        /// Commits the current block.
        /// </summary>
        /// <remarks>
        /// <para>
        /// If the current block has content or leading spaces, this method starts a new block 
        /// based on the current block.
        /// </para>
        /// </remarks>
        private void CommitBlock()
        {
            if ((_currentBlock.HasContent) || (_currentBlock.LeadingSpaces > 0))
            {
                StartNewBlock(_currentBlock);
            }
        }

        /// <summary>
        /// Starts a new block based on a parent block.
        /// </summary>
        /// <param name="parentBlock">The parent block.</param>
        private void StartNewBlock(HtmlBlock parentBlock)
        {
            Safety.AssertArgumentNotNull("parentBlock", parentBlock);

            _currentBlock = new HtmlBlock(parentBlock);
            _currentLine.AddBlock(_currentBlock);
        }

        #endregion // Implementation
    }

}
